package hypeerweb;

import communications.GlobalObjectId;
import communications.LocalObjectId;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.*;

/**
 * The Class Node, Is each of the parts in the HyPeerWeb<br/>
 * <br/>
 *
 * <b>Domain:</b>
 * 		webID		: WebId
 * 		neighbors	: ArrayList&gtNode&lt
 *		surNeighbors	: ArrayList&gtNode&lt
 *		invNeighbors	: ArrayList&gtNode&lt
 *		fold		: Node
 *		surFold		: Node
 *		invFold		: Node
 *		state		: NodeState
 * 
 * <b>Invariants:</b>
 * 		The value of the webID is &ge 0
 *		All neighbors, surrogate neibhors, or inverse surrogate neighbors
 *			appear only once in any of it's arrayLists
 *		All neighbors differ by only one bit from the webID's value
 *		All surrogate neighbors are the children of Nodes that would be
 *			neighbors if they existed
 *		The folds is the binary complement of the webID's value
 *		The Surrogate fold is the parent of the fold Node if it existed
 *		
 * 
 * @author Christopher Elliott
 */


public class Node implements Serializable {
   /**
	 * 
	 */
	private static final long serialVersionUID = -6160673332489368009L;

/**
    * The one and only NULL NODE.
    */
    public static Node NULL_NODE = new NullNode();
    
    protected static final int WEBID = -1;
    
   /**
    * Contains the WebId.
    */
    private WebId webID;
    
   /**
    * The List of all the neighbors.
    */
    private ArrayList<Node> neighbors;
    
   /**
    * The List of all the surrogate neighbors.
    */
    private ArrayList<Node> surNeighbors;
    
   /**
    * The List of all the inverse surrogate neighbors.
    */
    private ArrayList<Node> invNeighbors;
    
   /**
    * A pointer to the fold node.
    */
    private Node fold;
    
   /**
    * A pointer to the surrogate fold node.
    */
    private Node surFold;
    
   /**
    * A pointer to the inverse surrogate fold node
    */
    private Node invFold;
    
   /**
    * Contains the node's state
    */
    private NodeState state;
    
   /**
    * Contains the node's content
    */
    private Contents content = new Contents();
    
    private LocalObjectId localID;

    /**
	 * Constructs a node.
	 *
	 * @param id 
	 * @pre id &ge 0
	 * @post this.webID = new WebId(id)
	 */
    public Node(int id) {
//	assert id >= 0;

        webID = new WebId(id);
        initializeNode(id);
    }

    /** constructor with WebId and Height
	 * 
	 * @param id
	 * @param size
	 * @pre id &ge -1 and size &ge -1
	 * @post this.webID = new WebId(id, size)
	 */
    public Node(int id, int size) {
//		boolean check = true;
//
//		if (id == WEBID && size > WEBID) {
//			check = false;
//		}

//		assert check;
//		assert id >= WEBID;

        webID = new WebId(id, size);
        initializeNode(id);
    }
    
	/**
	 * Constructor helper. Initializes common variables between separate 
	 * constructors
	 *
	 * @param id
	 * @pre None
	 * @post neighbors, invNeighbor, surNeighbors = new ArrayList()
	 *	 fold = this
	 *	 surFold, invFold = NULL_NODE
	 *	 state = HypercubeCapState.getSingleton()
	 */
	private void initializeNode(int id)
	{
		neighbors = new ArrayList<Node>();
        invNeighbors = new ArrayList<Node>();
        surNeighbors = new ArrayList<Node>();
        fold = this;
        surFold = NULL_NODE;
        invFold = NULL_NODE;
        state = HypercubeCapState.getSingleton();
	}
	
   /**
    * Adds Down Pointer Node to list
    *
    * @param downPointer
    * @pre this's parent is downPointer's neighbor
    * @post downPointer is added to the SurNeighbors list
    */
    public void addDownPointer(Node downPointer) {
//    	    assert isSurNeighborOf(downPointer);
    	    
        surNeighbors.add(downPointer);
        webID.incrementHeight();
    }
    
   /**
    * Remove the smallest surrogate nieghbor from the list and change the state
    * to standardNodeState if the surNeighbors list is empty aftwards
    *
    * @param newNeighbor
    * @pre None
    * @post surNeighbors.size() == 0 OR 1 less then it was
    */
    public void alertNewNeighbor(Node newNeighbor) {
    	addNeighbor(newNeighbor);
    	
    	if (surNeighbors.size() > 0)
    	{
    		surNeighbors.remove(0);
    		webID.decrementHeight();
    		
    		if (surNeighbors.isEmpty() && fold.getWebIdValue() != 0)
    		{
    			state = StandardNodeState.getSingleton();
    		}
    	}
    	updateDatabase();
    }
    
   /**
    *  Calls addUpPointer method
    *
    * @param newSurrogate
    */
    public void alertNewSurrogateNeighbor(Node newSurrogate) {
    	addUpPointer(newSurrogate);
    	updateDatabase();
    }
    
   /**
    * sets the new fold and removes previous surrogate fold
    *
    * @param newFold
    * @pre None
    * @post fold = newFold
    *	    surFold = NULL_NODE
    */
    public void alertNewFold(Node newFold) {
    	if (fold != NULL_NODE)
    		invFold = fold;
    	
		fold = newFold;
		surFold = NULL_NODE;
    	updateDatabase();
    }
    
   /**
    * Add a new node to the neighbors list
    *
    * @param neighbor
    * @pre this is a neighbor of neighbor param
    * @post the neighbor param is added to the neighbors list
    */
    public void addNeighbor(Node neighbor) {
//        assert isNeighborOf(neighbor);
    
    	neighbors.add(neighbor);
    	webID.incrementHeight();
    }
    
   /**
    * check if the list is sorted by webId
    *
    * @param nodes
    * @pre None
    * @post None
    */
//    private boolean isSorted(ArrayList<Node> nodes)
//    {
//    	for (int i = 1; i < nodes.size(); ++i)
//    	{
//    		if (nodes.get(i-1).getWebId().getValue() >= nodes.get(i).getWebId().getValue())
//    			return false;
//    	}
//    	
//    	return true;
//    }
    
   /**
    * 
    * @return the smallest childless neighbor this node has smaller than itself, or self is such node doesn't exist
    */
    public Node getSmallestChildlessNeighbor()
    {
//    	assert isSorted(neighbors);
    	
    	if (invNeighbors.size() == neighbors.size())
    		return this;
    	
    	Node result =  neighbors.get(invNeighbors.size());
    	
    	if (result.getWebIdValue() < webID.getValue())
    		return result;
    	else
    		return this;
    }
    
   /**
    * @return the number of inverse surrogate neighbors
    */
//    public int getInvNeighborSize()
//    {
//    	return invNeighbors.size();
//    }

   /**
    * Add a new child node to the insertion point
    *
    * @param newChild
    * @pre this node is the insertion point
    * @post newChild is now this's largest neighbor and newChild has all its
    *       connections set
    */
    public void addNewChild(Node newChild) {
        // Set new node's stuff
    	newChild.setWebId(getWebId().createChildsWebId());
    	newChild.setState(state.getChildNodeState(this));
        
        Connections childConnections = getChildConnections();
        state = StandardNodeState.getSingleton();

        invNeighbors.clear();
        
        if (invFold != NULL_NODE)
        {
        	invFold = NULL_NODE;
        }
        else
        {
        	surFold = fold;
        	fold = NULL_NODE;
        }
        
        newChild.setConnections(childConnections);
    }

   /**
    * Add a new node when this is a node in the hypeerweb
    *
    * @param newNode
    * @pre this is in the hypeerweb
    * @post newNode is in the hypeerweb and the hypeerweb has all the nodes
    *	    it previously had in the same possitions they were in
    */
    public void addToHyPeerWeb(Node newNode) {
        
        // Get insertion point
        Node capNode = this;
        Node lastNode = null;
        while (!equals(capNode, lastNode)) {
        	lastNode = capNode;
        	capNode = capNode.getCapNode();
        }
        
        Node insertionPoint = capNode;
        lastNode = null;
        while (!equals(insertionPoint, lastNode)) {
        	lastNode = insertionPoint;
        	insertionPoint = insertionPoint.getInsertionNode();
        }
        
        insertionPoint.addNewChild(newNode);
	}
    
    public void replaceNeighbor(Node oldNode, Node newNode)
    {
    	int prevIndex = neighbors.indexOf(oldNode);
    	neighbors.set(prevIndex, newNode);
    }
    
    public void replaceSurrogateNeighbor(Node oldNode, Node newNode)
    {
    	int prevIndex = surNeighbors.indexOf(oldNode);
    	surNeighbors.set(prevIndex, newNode);
    }
    
    public void replaceInvSurrogateNeighbor(Node oldNode, Node newNode)
    {
    	int prevIndex = invNeighbors.indexOf(oldNode);
    	invNeighbors.set(prevIndex, newNode);
    }
    
    public void alertNewNeighbors(Node deadNode)
    {	
    	for (Node neighbor : neighbors)
    	{
    		neighbor.replaceNeighbor(deadNode, this);
    	}

    	for (Node surNeighbor : surNeighbors)
    	{
    		surNeighbor.replaceInvSurrogateNeighbor(deadNode, this);
    	}

    	for (Node invNeighbor : invNeighbors)
    	{
    		invNeighbor.replaceSurrogateNeighbor(deadNode, this);
    	}
    }
    
    public void alertNewFolds()
    {
    	if (fold != NULL_NODE)
    	{
    		fold.setFold(this);
    	}
    	
    	if (surFold != NULL_NODE)
    	{
    		surFold.setInvFold(this);
    	}
    	
    	if (invFold != NULL_NODE)
    	{
    		invFold.setSurFold(this);
    	}
    }
    
    public List<Node> getNeighbors()
    {
    	return neighbors;
    }
    
    public List<Node> getSurNeighbors()
    {
    	return surNeighbors;
    }
    
    public List<Node> getInvNeighbors()
    {
    	return invNeighbors;
    }
    
    /**
     * This node takes the place of the deadNode
     * and replaces deadNode in all of the connections of the
     * nodes adjacent to daeadNode
     * @pre this has no connections
     * @post all nodes that had a connection to deadNode is now connected to this 
     * node no node in the hypeerweb has a connection to dead node. this takes on the web id of deadNode
     * @param deadNode
     */
    public void replaceNode(Node deadNode) {
    	
    	webID = deadNode.getWebId();
    	
    	if (equals(deadNode, deadNode.getFold()))
    		fold = this;
    	else
    		fold = deadNode.getFold();
    	
    	surFold = deadNode.getSurFold();
    	invFold = deadNode.getInvFold();
    	
    	neighbors = new ArrayList<Node>(deadNode.getNeighbors());
    	surNeighbors = new ArrayList<Node>(deadNode.getSurNeighbors());
    	invNeighbors = new ArrayList<Node>(deadNode.getInvNeighbors());
    	
    	state = deadNode.getState();

    	alertNewNeighbors(deadNode);
    	alertNewFolds();
    }
     
    /**
     * Deletes this node from the end of the hypeerweb. Opposite of insertion
     * @pre this node is the last node in the hypeerweb
     * @post this is no longer part of the hypeerweb
     */
    public void disconnectFromWeb() {
    	foldDisconnect();
    	neighborDisconnect();
    	neighbors.clear();
    	surNeighbors.clear();
    	invNeighbors.clear();
    	fold = this;
    	invFold = NULL_NODE;
    	surFold = NULL_NODE;
    	//webID = WebId.NULL_WEB_ID;
        state = HypercubeCapState.getSingleton();
    }
    
    /**
     * this is the dying node to be removed
     */
    public void removeFromHyPeerWeb() {
    	
    	if (fold == this)
    		return; // this is the only node there is
    	
    	Node lastNode = state.findDeletionPoint(this);
    	lastNode.disconnectFromWeb();
    	
    	if (!equals(lastNode, this))
    	{
    		lastNode.replaceNode(this);
    	}
    }
    
   /**
    * @return cap Node
    */
    public Node getCapNode() {
        return state.getCapNode(this);
    }
    
   /**
    * @return insertion node 
    */
    public Node getInsertionNode() {
        return state.getInsertionNode(this);
    }
    
    public Node getSmallestNeighbor(){
        return neighbors.get(0);
    }
    
    
    /**
     * Finds all the connections for a new Child node
     *
     * @pre this is a newly added child node
     * @post returns all the connections for that child
     */
    public Connections getChildConnections() {
    	ArrayList<Node> childNeighbors = new ArrayList<Node>(invNeighbors.size() + 1);
    	ArrayList<Node> childSurrogateNeighbors = new ArrayList<Node>(neighbors.size());
    	
    	childNeighbors.add(this);
    	childNeighbors.addAll(invNeighbors);

    	for (Node neighbor : neighbors)
    	{
    		if (neighbor.getWebIdValue() > webID.getValue())
    		{
    			childSurrogateNeighbors.add(neighbor);
    		}
    	}
    	
    	Node childFold;
    	
    	if (invFold != NULL_NODE)
		{
    		childFold = invFold;
		}
    	else
    	{
    		childFold = fold;
    	}
    	
    	return new Connections(childNeighbors, childSurrogateNeighbors, childFold);
    }
    
//    public Connections getConnections() {
//    	throw new UnsupportedOperationException("Unsupported Operation");
//    }

   /**
    * sets all the connection for a node
    *
    * @param connections
    * @pre connections has the value of all the neighbors, surrogate neighbors,
    *	   and the fold
    * @post all list and the fold are updated in this node
    */
    public void setConnections(Connections connections) {
         neighbors = new ArrayList<Node>(connections.getNeighbors());
         surNeighbors = new ArrayList<Node>(connections.getSurrogateNeighbors());
         
         fold = connections.getFold();
         
         for (Node neighbor : neighbors) {
        	 neighbor.alertNewNeighbor(this);
         }
         
         for (Node surNeighbor : surNeighbors) {
        	 surNeighbor.alertNewSurrogateNeighbor(this);
         }
         
         webID = new WebId(getWebIdValue(), neighbors.size() + surNeighbors.size());
         
         fold.alertNewFold(this);
	}
    
//    public Node getNextNeighborDown() {
//    	throw new UnsupportedOperationException("Unsupported Operation");
//    }
    
   /**
    * 
    * @return the neighbor node with the largest value
    */
    public Node getLargestNeighbor() {
    	if (neighbors.size() == 0)
    		return null;
    	else
    		return neighbors.get(neighbors.size() - 1);
    }
    
   /**
    * @return the surrogate neighbor with the largest value
    */
    public Node getLargestSurrogateNeighbor(){
    	return surNeighbors.get(surNeighbors.size() - 1);
    }
    
   /**
    * @return a SimplifiedNodeDomain of the node values
    */
    public SimplifiedNodeDomain constructSimplifiedNodeDomain() {
		// get the int values of all the Nodes connected to this Node
		int foldInt = fold.getWebIdValue();
		int surFoldInt = surFold.getWebIdValue();
		int invFoldInt = invFold.getWebIdValue();

		HashSet<Integer> neighborIDs = new HashSet<Integer>(neighbors.size());
		for (Node neighbor : neighbors) {
			neighborIDs.add(neighbor.getWebIdValue());
		}

		HashSet<Integer> invSurrogateIDs = new HashSet<Integer>(invNeighbors.size());
		for (Node neighbor : invNeighbors) {
			invSurrogateIDs.add(neighbor.getWebIdValue());
		}

		HashSet<Integer> surrogateIDs = new HashSet<Integer>(surNeighbors.size());
		for (Node neighbor : surNeighbors) {
			surrogateIDs.add(neighbor.getWebIdValue());
		}

		return new SimplifiedNodeDomain(
				webID.getValue(),
				webID.getHeight(),
				neighborIDs,
				invSurrogateIDs,
				surrogateIDs,
				foldInt,
				surFoldInt,
				invFoldInt,
				state.getID()
			);
    }

   /**
    * set inverse surrogate neighbor pointers
    */
    public void addUpPointer(Node upPointer) {
        invNeighbors.add(upPointer);
    }

   /**
    * set webID
    */
    public WebId getWebId() {
        return webID;
    }

   /**
    * remove downPointer from the surrogate neighbors list
    */
    public void removeDownPointer(Node downPointer) {
        surNeighbors.remove(downPointer);
        webID.decrementHeight();
    }

   /**
    * remove neighbor from the neighbors list
    */
    public void removeNeighbor(Node neighbor) {
        neighbors.remove(neighbor);
        webID.decrementHeight();
    }

   /**
    * remove upPointer from the inverse surrogate neighbors list
    */
    public void removeUpPointer(Node upPointer) {
        invNeighbors.remove(upPointer);
    }

   /**
    * set fold to newFold
    *
    * @param newFold
    * @pre None
    * @post fold = newFold
    */
    public void changeFold(Node newFold) {
//		assert isFoldOf(newFold);

		fold = newFold;
    }
    
   /**
    * set fold to newFold
    *
    * @param newFold
    * @pre None
    * @post fold = newFold
    */
    public void setFold(Node newFold) {
//		assert isFoldOf(newFold);

		changeFold(newFold);
    }
    
    public void setSurFold(Node node) {
    
		surFold = node;
    }
    
    public void setInvFold(Node node) {
    
		invFold = node;
    }
    
   /**
    * @return fold
    */
    public Node getFold() {
    	return fold;
    }
    
    public Node getSurFold() {
    	return surFold;
    }
    
    public Node getInvFold() {
    	return invFold;
    }

   /**
    * set inverse fold to newInverseSurrogateFold
    *
    * @param newInverseSurrogateFold
    * @pre None
    * @post invFold = newInverseSurrogateFold
    */
    public void setInverseSurrogateFold(Node newInverseSurrogateFold) {
//    	        assert isInvSurFoldOf(newInverseSurrogateFold);
		invFold = newInverseSurrogateFold;
    }

   /**
    * set surrogate fold to newSurrogateFold
    *
    * @param newSurrogateFold
    * @pre None
    * @post invFold = newSurrogateFold
    */
    public void setSurrogateFold(Node newSurrogateFold) {
//    	        assert isSurFoldOf(newSurrogateFold);
		surFold = newSurrogateFold;
    }

   /**
    * set webID to webId
    *
    * @param webId
    * @pre None
    * @post webID = webId
    */
    public void setWebId(WebId webId) {
        webID = webId;
    }
    
    // handles the fold methods for disconnecting a node
    public void foldDisconnect()
    {
    	    Node parent = neighbors.get(0);
    	    
    	    if(parent.getSurFold() != NULL_NODE)
    	    {
	    	    parent.setFold(fold);
	    	    parent.setSurrogateFold(NULL_NODE);
	    	    fold.setFold(parent);
	    	    fold.setInverseSurrogateFold(NULL_NODE);
    	    }
    	    else
    	    {
	    	    parent.setInverseSurrogateFold(fold);
	    	    fold.setSurrogateFold(parent);
	    	    fold.setFold(NULL_NODE);
    	    }
    	    
    	    if (getWebHeight(this) == 1)
    	    {
    	    	parent.setFold(parent);
    	    	parent.setInverseSurrogateFold(NULL_NODE);
    	    	parent.setSurrogateFold(NULL_NODE);
    	    }
    }
    
    public static boolean foldDiconnectTester()
    {
    	    ArrayList<Node> testNode = new ArrayList<Node>();
    	    testNode.add(new Node(0));
    	    testNode.add(new Node(1));
    	    Node start = testNode.get(0);
    	    Node end = testNode.get(1);

    	    start.addToHyPeerWeb(end);
    	    
    	    end.foldDisconnect();
    	    
    	    // test where height is 1
    	    if(!start.getFold().equals(start)) {return false;}
    	    if(!start.getSurFold().equals(NULL_NODE)) {return false;}
    	    if(!start.getInvFold().equals(NULL_NODE)) {return false;}
    	    
    	    testNode.clear();
    	    
    	    testNode.add(new Node(0));
    	    testNode.add(new Node(1));
    	    testNode.add(new Node(2));
    	    testNode.add(new Node(3));
    	    
    	    for(int x = 0; x < 3; x++)
    	    {
    	    	    start = testNode.get(x);
    	    	    end = testNode.get(x+1);
    	    
    	    	    start.addToHyPeerWeb(end);
    	    }
    	    
    	    end.foldDisconnect();
    	    
    	    // test where the parent has no surrogate Fold
    	    if(testNode.get(0).surFold != testNode.get(1)) {return false;}
    	    if(testNode.get(1).fold != testNode.get(2)) {return false;}
    	    if(testNode.get(1).invFold != testNode.get(0)) {return false;}
    	    if(testNode.get(2).fold != testNode.get(1)) {return false;}
    	    
    	    start.foldDisconnect();
    	    start = testNode.get(0);
    	    end = testNode.get(1);
    	    
    	    // test where the parent has a surrogate Fold
    	    if(start.fold != end) {return false;}
    	    if(end.fold != start) {return false;}
    	    if(start.surFold != NULL_NODE) {return false;}
    	    if(end.surFold != NULL_NODE) {return false;}
    	    if(start.invFold != NULL_NODE) {return false;}
    	    if(end.invFold != NULL_NODE) {return false;}
    	    return true;
    	    
    }
    
    public void neighborDisconnect(){
    	Node parent = neighbors.get(0);
    	
    	for (int i = 0; i < neighbors.size(); i++){
    		if (neighbors.get(i) == parent){
    			parent.removeNeighbor(this);
    		}
    		else
    		{
    			neighbors.get(i).replaceNeighborWithSurrogate(this, parent);
    		}
    	}
    	
    	for (int i = 0; i < surNeighbors.size(); i++){
    		surNeighbors.get(i).removeUpPointer(this);
    	}
        
        ArrayList<Node> parentInvSurNeighbors = new ArrayList<Node>(neighbors);
        parentInvSurNeighbors.remove(parent);
        
        parent.setInverseNeighbors(parentInvSurNeighbors);
    	parent.setState(state.getParentNodeState(this));
    }
    
    public void setInverseNeighbors(List<Node> nodes)
    {
    	invNeighbors = new ArrayList<Node>(nodes);
    }
    
    public void replaceNeighborWithSurrogate(Node replace, Node surrogate){
    	neighbors.remove(replace);
    	surNeighbors.add(surrogate);
    	
    	state = DownPointingNodeState.getSingleton();
    }

    // get the webID height
    private int getWebHeight(Node nodeCheck) {
		return nodeCheck.getWebId().getHeight();
    }

    // check if Node is a Fold
//    private boolean isFoldOf(Node node) {
//    	return webID.isFoldOf(node.getWebId());
//    }
//
//    // check if Node is a Surrogate Fold
//    private boolean isSurFoldOf(Node node) {
//    	return webID.isSurrogateFoldOf(node.getWebId());
//    }
//
//    // check if Node is an Inverse Surrogate Fold
//    private boolean isInvSurFoldOf(Node node) {
//    	return webID.isInverseSurrogateFoldOf(node.getWebId());
//    }
//
//    // check if Node is a Neighbor
//    private boolean isNeighborOf(Node node) {
//        assert node != null;
//        return webID.isNeighborOf(node.getWebId());
//    }
//
//    // check if Node is a Surrogate Neighbor
//    private boolean isSurNeighborOf(Node node) {
//    	return webID.isSurrogateNeighborOf(node.getWebId());
//    }
        
    // changes the current State
    public void setState(NodeState modify) {
    	    state = modify;
    }

    // gives the length of the Neighbor list
//    private int getNeighborNum() {
//        return neighbors.size();
//    }

    public void updateDatabase() {
    	
    }
    
    
   /**
    * sets all the connection for a node
    *
    * @param visitor
    * @param parameters
    * @pre visitor != null AND parameters != null AND visitor.visit(this, parameters).precondition
    * @post visitor.visit(this, parameters).postcondition
    */
    public void accept(Visitor visitor, Parameters parameters)
    {   	
    	visitor.visit(this, parameters);
    }
    
   /**
    * @return the content of the Node
    */
    public Contents getContents()
    {
    	return content;
    }
    
    public Node retrieveNode(WebId id)
    {
    	Node targetNode = this;
    	int wegIdValue = id.getValue();
    	
    	while (targetNode.getWebIdValue() != wegIdValue)
    	{
    		Node nextNode = targetNode.closerNode(id);
    		
    		if (equals(nextNode, targetNode))
    			return null;
    		
    		targetNode = nextNode;
    	}
    	
    	return targetNode;
    }
    
    public Node closerNode(WebId id)
    {
    	    int distance = getDistance(id, getWebId());
    	    int test;
    	    Node result = this;
    	    
    	    for (Node neighbor : neighbors) {
    	    	    test = getDistance(id, neighbor.getWebId());
    	    	    if(test < distance)
    	    	    {
    	    	    	    distance = test;
    	    	    	    result = neighbor;
    	    	    }
    	    	    	    
    	    }

    	    for (Node neighbor : invNeighbors) {
    	    	    test = getDistance(id, neighbor.getWebId());
    	    	    if(test < distance)
    	    	    {
    	    	    	    distance = test;
    	    	    	    result = neighbor;
    	    	    }
    	    	    
    	    }

    	    for (Node neighbor : surNeighbors) {
    	    	    test = getDistance(id, neighbor.getWebId());
    	    	    if(test < distance)
    	    	    {
    	    	    	    distance = test;
    	    	    	    result = neighbor;
    	    	    }
    	    }
    	    
    	    if (fold != NULL_NODE)
    	    {
	    	    test = getDistance(id, fold.getWebId());
	    	    if(test < distance) {distance = test; result = fold;}
    	    }
    	    
    	    return result;
    }
    
    private static int getDistance(WebId start, WebId end)
    {
    	    int checker = start.getValue() ^ end.getValue();
    	    int counter = 0;
    	    
    	    while (checker > 0) {
    	    	    if((checker & 1) == 1)
    	    	    	    counter++;
    	    	    checker = checker >> 1;
    	    }
    	    
    	    return counter;
    }
    
    private static void printNode(Node target, StringBuilder result)
    {
		result.append("<" + target.getWebIdValue() + " " + target.toString() + ">");
    }
    
    private static void buildNeighborList(ArrayList<Node> nodeList, StringBuilder result)
    {
    	result.append('[');
    	
    	int i = 0;
    	
    	for (Node node : nodeList)
    	{
    		if (i > 0)
    			result.append(", ");

    		printNode(node, result);
    		
    		++i;
    	}
    	
    	result.append(']');
    }
    
    public String buildDebugString()
    {
    	StringBuilder result = new StringBuilder();
    	
    	result.append(toString());
    	result.append('\n');
    	
    	result.append("webID: " + webID.toString() + "\n");
    	
    	result.append("neighbors: ");
    	buildNeighborList(neighbors, result);
    	result.append('\n');

    	result.append("surNeighbors: ");
    	buildNeighborList(surNeighbors, result);
    	result.append('\n');

    	result.append("invNeighbors: ");
    	buildNeighborList(invNeighbors, result);
    	result.append('\n');
    	
    	result.append("fold: ");
    	printNode(fold, result);
    	result.append("\n");
    	
    	result.append("surFold: ");
    	printNode(surFold, result);
    	result.append("\n");
    	
    	result.append("invFold: ");
    	printNode(invFold, result);
    	result.append("\n");
    	
    	result.append("state: " + state.toString());
    	
    	return result.toString();
    }
    
    public boolean isLocalNode()
    {
    	return true;
    }
    
    public LocalObjectId getLocalObjectId()
    {
    	return localID;
    }
    
    public void setLocalObjectId(LocalObjectId value)
    {
    	localID = value;
    }
	
	public Object writeReplace() throws ObjectStreamException
	{
		if (localID != null)
			return new NodeProxy(new GlobalObjectId(localID), webID.getValue());
		else
			return this;
	}
    
    public int getWebIdValue()
    {
    	    return webID.getValue();
    }
    
    public NodeState getState()
    {
    	    return state;
    }

    @Override
    public boolean equals(Object other)
    {
    	if (this == other)
    		return true;
    	if (!(other instanceof Node))
    		return false;
    	
    	Node otherNode = (Node)other;
    	return otherNode != null && getWebIdValue()  == otherNode.getWebIdValue();
    }
    
    public static boolean equals(Node a, Node b)
    {
    	return a == b || (a != null && b != null && a.equals(b));
    }

	void removeLocalObjectId() {
		this.localID = null;
	}
}
